#ifndef __FOURIERTRON__
#define __FOURIERTRON__

#include "cxfloat.h"
#include <vector>
#include "Pincushion.h"


class FourierTron
{
  public:
    FourierTron(std::vector<SeamStress::Seamstress*> *ss, unsigned long int nt=1, mpfr_prec_t p=4, unsigned long int cache=0x40000);
    ~FourierTron();
    
    void calculateTwiddle(unsigned long int N);
    void calculateTwiddleBase(unsigned long int N);
    
    //sets val to exp(-2*pi*I*j/(2^i))
    void twiddleVal(unsigned long int i, unsigned long int j, cxfloat &val);
    void copyTwiddle(std::vector<std::vector<cxfloat> >& dest, mpfr_prec_t p);
    
    //take fft of first N entries of input, in place.  N must be a power of 2
    void CT_FFT(std::vector<cxfloat> &input, unsigned long int N);
    //take fft of first N entries of vector, and store in first N entries of output.  N must be a power of 2
    void CT_FFT(const std::vector<cxfloat> &input, std::vector<cxfloat> &output, unsigned long int N);
    void CT_FFT(const refloat_vec &input, std::vector<cxfloat> &output, unsigned long int N);
    void inverseCT_FFT(std::vector<cxfloat> &input, unsigned long int N);
    void inverseCT_FFT(const std::vector<cxfloat> &input, std::vector<cxfloat> &output, unsigned long int N);
    void inverseCT_FFT(const std::vector<cxfloat>& input, refloat_vec& output, unsigned long int N);
    
    void getPi(cxfloat& val);
    void getPi(refloat& val);
    
  protected:
    SeamStress::Pincushion<FourierTron> pins;
    
    mpfr_prec_t prec;
    
    cxfloat pi;
    
    unsigned long int nthreads;
    
    static std::vector<std::vector<cxfloat> > twiddle;
    
    
  private:
    //contains one entry per thread
    std::vector<cxfloat> threadvec1;
    std::vector<cxfloat> threadvec2;
    
    cxfloat tempval1;
    cxfloat tempval2;
    
    unsigned long int thread_tot;
    unsigned long int thread_var1;
    unsigned long int thread_var2;
    unsigned long int thread_var3;
    unsigned long int thread_var4;
    
    std::vector<std::vector<std::vector<unsigned long int> > > reversal;
    
    std::vector<cxfloat> tempvec1;
    std::vector<cxfloat> tempvec2;
    
    std::vector<cxfloat> *tempvecpointer1;
    const std::vector<cxfloat> *tempvecpointer2;
    
    refloat_vec *retempvecpointer1;
    const refloat_vec *retempvecpointer2;
    
    std::vector<std::vector<cxfloat> > localvec;
    
    unsigned long int twid;
    unsigned long int fft_size;
    
    //cache size in bytes
    unsigned long int cache_size;
    unsigned long int step_depth;
    
    void bitReverse(unsigned long int logN);
    
    // ****************************************************
    struct CT_FFT_mul3threadsSplit_TYPE
    {
      const cxfloat_vec* input;
      cxfloat_vec* vec0;
      cxfloat_vec* vec1;
      cxfloat_vec* vec3;
      cxfloat_vec* vec4;
      unsigned int tthreads;
      unsigned long int N;
    };
    mutable CT_FFT_mul3threadsSplit_TYPE CT_FFT_mul3threadsSplit_DATA;
    void CT_FFT_mul3threadsSplit(void* arg);
    void CT_FFT_mul3threads(cxfloat_vec& input, unsigned long int N);
    // ****************************************************
    
    // ****************************************************
    struct CT_FFT_subFFT_TYPE
    {
      cxfloat_vec* input;
      std::vector<SeamStress::Seamstress*>* ss;
    };
    void CT_FFT_subFFT(void* arg);
    // ****************************************************
    
    // ****************************************************
    struct CT_FFT_subArrange_TYPE
    {
      cxfloat_vec* input;
      cxfloat_vec* output;
      unsigned long int N;
    };
    CT_FFT_subArrange_TYPE CT_FFT_subArrange_DATA;
    void CT_FFT_subArrange(void* arg);
    void CT_FFT_copyArrange(void* arg);
    struct CT_FFT_subTwiddle_TYPE
    {
      cxfloat_vec* even;
      cxfloat_vec* even1;
      cxfloat_vec* odd1;
      cxfloat_vec* output;
      unsigned long int N;
    };
    CT_FFT_subTwiddle_TYPE CT_FFT_subTwiddle_DATA;
    void CT_FFT_subTwiddle(void* arg);
    // ****************************************************
    
    
    void twiddleThread1(void *arg);
    void twiddleThread2(void *arg);
    void CT_FFT_swap(void *arg);
    void CT_FFT_step(void *arg);
    void CT_FFT_step_cache(void *arg);
    void CT_FFT_tiptoe(void *arg);
    void CT_FFT_march(void *arg);
    void CT_FFT_march_cache(void *arg);
    void CT_FFT_march_cache_local(void *arg);
    void inverseCT_FFTThread1(void *arg);
    void CT_FFT_interleave(void *arg);
    void CT_FFT_spread(void *arg);
    void inverseCT_FFT_interleave(void *arg);
    void inverseCT_FFT_spread(void *arg);
    void clearCache(void *arg);
    void setCachePrec(void *arg);
};

inline void FourierTron::getPi(cxfloat& val){val.set(pi);}
inline void FourierTron::getPi(refloat& val){pi.get_real(val);}


#endif
